﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using Community.DynamicsCrmWebResourceTool.UI;
using EnvDTE;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Community.DynamicsCrmWebResourceTool.Models;

namespace Community.DynamicsCrmWebResourceTool
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the information needed to show this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideAutoLoad(UIContextGuids80.SolutionExists)]
    [Guid(GuidList.guidDynamicsCrmWebResourceToolPkgString)]
    public sealed class DynamicsCrmWebResourceToolPackage : Package
    {
        private static string[] fileExtensions =
        {
            ".html",
            ".htm",
            ".js",
            ".css",
            ".gif",
            ".jpg",
            ".png",
            ".ico",
            ".xml",
            ".xsl",
            ".xslt",
            ".xap"
        };

        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public DynamicsCrmWebResourceToolPackage()
        {
            Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overridden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initialization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Debug.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                // Create the command for the menu item.
                CommandID menuMapCommandID = new CommandID(GuidList.guidDynamicsCrmWebResourceToolCmdSet, (int)PkgCmdIDList.cmdidMapPublishWebResource);
                var menuMapItem = new OleMenuCommand(MenuItemCallback, menuMapCommandID);
                menuMapItem.BeforeQueryStatus += menuCommand_BeforeQueryStatus;
                mcs.AddCommand(menuMapItem);

                CommandID menuPublishCommandID = new CommandID(GuidList.guidDynamicsCrmWebResourceToolCmdSet, (int)PkgCmdIDList.cmdidPublishCurrentFile);
                var menuPublishItem = new OleMenuCommand(PublishMenuItemCallback, menuPublishCommandID);
                menuPublishItem.BeforeQueryStatus += menuPublishItem_BeforeQueryStatus;
                mcs.AddCommand(menuPublishItem);
            }

            GetOutputWindowHandler();
        }

        #endregion

        void menuPublishItem_BeforeQueryStatus(object sender, EventArgs e)
        {
            EnableDisableMenu(sender);
        }

        private void PublishMenuItemCallback(object sender, EventArgs e)
        {
            string projectFullPath;
            string currentDocument;

            GetProjectPathAndFileName(out projectFullPath, out currentDocument);

            Settings settings = new Settings();
            settings.SettingsFilePath = projectFullPath;
            settings.SettingsFileName = Constants.SettingsFileName;
            WriteToVisualStudioOutputWindow("Loading settings from file...");
            if (settings.LoadConnection())
            {
                
                WriteToVisualStudioOutputWindow("Settings loaded. Trying to connect to Dynamics CRM...");
                Crm.Helper.GetServiceProxy(settings.BusinessServiceUrl, settings.Username, settings.Password, settings.Domain);
                WriteToVisualStudioOutputWindow("Connected to Dynamics CRM");
                WriteToVisualStudioOutputWindow("Looking for Web Resource Id for the selected file...");
                FileMap mapping = settings.GetWebResourceIdForMapping(currentDocument);

                if (mapping != null && !mapping.WebResourceId.Equals(Guid.Empty))
                {
                    WriteToVisualStudioOutputWindow("Web Resource Id found. Updating the content now...");
                    Crm.Helper.UpdateWebResource(
                        filePath: projectFullPath + currentDocument,
                        displayName: null,
                        description: null,
                        webResourceId: mapping.WebResourceId
                        );
                    settings.LoadMappings(Guid.Parse(mapping.SolutionId));
                    settings.UpdateUploadDate(currentDocument, Guid.Parse(mapping.SolutionId), DateTime.Now);
                    WriteToVisualStudioOutputWindow("Content updated. Publishing now...");
                    Crm.Helper.PublishWebResource(new List<Guid>() { mapping.WebResourceId });
                    settings.UpdatePublishDate(new List<Guid> {mapping.WebResourceId}, DateTime.Now);
                    settings.SaveMapping();
                    WriteToVisualStudioOutputWindow("Publishing succeded.");
                }
                else
                {
                    WriteToVisualStudioOutputWindow("Selected file is not mapped to any Web Resource in Dynamics CRM. Opening mapping window now...");
                    OpenMappingForm(projectFullPath, currentDocument);
                }
            }
            else
            {
                WriteToVisualStudioOutputWindow("Could not find connection settings. Opening mapping window now...");
                OpenMappingForm(projectFullPath, currentDocument);
            }
        }

        private static void OpenMappingForm(string projectFullPath, string currentDocument)
        {
            //Show the form
            MapAndPublishForm form = new MapAndPublishForm();
            form.ProjectPath = projectFullPath;
            form.SelectedFileName = currentDocument;
            form.ShowDialog();
        }

        private void EnableDisableMenu(object menu)
        {
            // get the menu that fired the event
            var menuCommand = menu as OleMenuCommand;
            if (menuCommand != null)
            {
                // start by assuming that the menu will not be shown
                menuCommand.Visible = true;
                menuCommand.Enabled = false;

                IVsHierarchy hierarchy = null;
                uint itemid = VSConstants.VSITEMID_NIL;

                if (!IsSingleProjectItemSelection(out hierarchy, out itemid)) return;
                // Get the file path and extension
                string itemFullPath = null;
                ((IVsProject)hierarchy).GetMkDocument(itemid, out itemFullPath);
                var transformFileInfo = new FileInfo(itemFullPath);

                // then check if the file has suported extension
                bool isSupportedFile = fileExtensions.Contains(transformFileInfo.Extension.ToLower());

                // if not leave the menu hidden
                if (!isSupportedFile) return;

                menuCommand.Visible = true;
                menuCommand.Enabled = true;
            }
        }

        private void menuCommand_BeforeQueryStatus(object sender, EventArgs e)
        {
            EnableDisableMenu(sender);
        }

        public static bool IsSingleProjectItemSelection(out IVsHierarchy hierarchy, out uint itemid)
        {
            hierarchy = null;
            itemid = VSConstants.VSITEMID_NIL;
            int hr = VSConstants.S_OK;

            var monitorSelection = Package.GetGlobalService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
            var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution;
            if (monitorSelection == null || solution == null)
            {
                return false;
            }

            IntPtr hierarchyPtr = IntPtr.Zero;
            IntPtr selectionContainerPtr = IntPtr.Zero;

            try
            {
                IVsMultiItemSelect multiItemSelect = null;
                hr = monitorSelection.GetCurrentSelection(out hierarchyPtr, out itemid, out multiItemSelect, out selectionContainerPtr);

                if (ErrorHandler.Failed(hr) || hierarchyPtr == IntPtr.Zero || itemid == VSConstants.VSITEMID_NIL)
                {
                    // there is no selection
                    return false;
                }

                // multiple items are selected
                if (multiItemSelect != null) return false;

                // there is a hierarchy root node selected, thus it is not a single item inside a project

                if (itemid == VSConstants.VSITEMID_ROOT) return false;

                hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
                if (hierarchy == null) return false;

                Guid guidProjectID = Guid.Empty;

                if (ErrorHandler.Failed(solution.GetGuidOfProject(hierarchy, out guidProjectID)))
                {
                    return false; // hierarchy is not a project inside the Solution if it does not have a ProjectID Guid
                }

                // if we got this far then there is a single project item selected
                return true;
            }
            finally
            {
                if (selectionContainerPtr != IntPtr.Zero)
                {
                    Marshal.Release(selectionContainerPtr);
                }

                if (hierarchyPtr != IntPtr.Zero)
                {
                    Marshal.Release(hierarchyPtr);
                }
            }
        }

        private void GetProjectPathAndFileName(out string projectPath, out string filename)
        {
            projectPath = null;
            filename = null;

            IVsHierarchy hierarchy = null;
            uint itemid = VSConstants.VSITEMID_NIL;

            if (!IsSingleProjectItemSelection(out hierarchy, out itemid)) return;

            // Get an instance of the currently running Visual Studio IDE
            DTE dte = (DTE)GetService(typeof(DTE));
            string solutionDir = System.IO.Path.GetDirectoryName(dte.Solution.FullName);

            List<string> projectNames = new List<string>();

            foreach (Project dteProject in dte.Solution)
            {
                string uniqueName = dteProject.UniqueName;
                projectNames.Add(uniqueName.Substring(uniqueName.LastIndexOf("\\")));
            }

            var vsProject = (IVsProject)hierarchy;

            string projectFullPath = null;
            if (ErrorHandler.Failed(vsProject.GetMkDocument(VSConstants.VSITEMID_ROOT, out projectFullPath))) return;

            //Remove project name from full path
            foreach (string p in projectNames)
            {
                projectFullPath = projectFullPath.Replace(p, "");
            }

            // Get the current document
            uint currentItemId;
            IntPtr hierPtr = IntPtr.Zero;
            IntPtr selPtr = IntPtr.Zero;
            IVsMultiItemSelect pMult;
            IVsMonitorSelection monSel = (IVsMonitorSelection)GetService(typeof(SVsShellMonitorSelection));
            int hr = monSel.GetCurrentSelection(out hierPtr, out currentItemId, out pMult, out selPtr);
            string currentDocument;
            hr = hierarchy.GetCanonicalName(currentItemId, out currentDocument);


            projectPath = projectFullPath;
            //filename = dte.ActiveDocument.FullName;
            //filename = filename.Replace(projectFullPath, "");
            filename = currentDocument.ToLower().Replace(projectFullPath.ToLower(), "");
        }

        /// <summary>
        /// This function is the callback used to execute a command when the a menu item is clicked.
        /// See the Initialize method to see how the menu item is associated to this function using
        /// the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void MenuItemCallback(object sender, EventArgs e)
        {
            string projectFullPath;
            string currentDocument;

            GetProjectPathAndFileName(out projectFullPath, out currentDocument);
            
            WriteToVisualStudioOutputWindow("Displaying Dynamic CRM Web Resource Tool main window");

            //Show the form
            OpenMappingForm(projectFullPath, currentDocument);
        }

        private static IVsOutputWindowPane outputPane;

        private static void WriteToVisualStudioOutputWindow(string message)
        {
            outputPane.Activate();
            outputPane.OutputString(Environment.NewLine + message);
        }

        private void GetOutputWindowHandler()
        {
            IVsOutputWindow outputWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;

            Guid guidGeneral = Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.GeneralPane_guid;
            
            int hr = outputWindow.CreatePane(guidGeneral, "Dynamics CRM Web Resource Tool", 1, 0);
            hr = outputWindow.GetPane(guidGeneral, out outputPane);
        }
    }
}
